package com.tgh.gles2.c8.pixbuffer;

import com.tgh.gles2.BaseRenderer;
import com.tgh.gles2.R;
import com.tgh.utils.GLBatch;
import com.tgh.utils.GLFrame;
import com.tgh.utils.GLFrustum;
import com.tgh.utils.GLGeometryTransform;
import com.tgh.utils.GLMatrixStack;
import com.tgh.utils.GLShaderManager;
import com.tgh.utils.GLShaderManager.ShaderType;
import com.tgh.utils.GLTools;
import com.tgh.utils.GLTriangleBatch;
import com.tgh.utils.Math3D;
import com.tgh.utils.Matrix44f;
import com.tgh.utils.Vector4f;

import android.content.Context;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.os.SystemClock;

import java.nio.ByteBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class PixBufferRenderer extends BaseRenderer {

    public PixBufferRenderer(Context context) {
        super(context);
        cameraFrame=new GLFrame();
        modelViewMatrix=new GLMatrixStack();
        projectionViewMatrix=new GLMatrixStack();
        transformPipleLine=new GLGeometryTransform();
        initVertices();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        initPrograms();
        shaderManager = new GLShaderManager();
        shaderManager.initStockShader();
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    }
    
    public void moveForward() {
        cameraFrame.moveForward(0.1f);
    }
    
    public void moveBack() {
        cameraFrame.moveForward(-0.1f);
    }
    
    public void turnLeft() {
        cameraFrame.rotateLocalY(10);
    }
    
    public void turnRight() {
        cameraFrame.rotateLocalY(-10);
    }
    
    public void accelerate() {
        float linear=SystemClock.elapsedRealtime()/1000*12.0f;
        speedFactor+=linear/2;
        if (speedFactor>6) {
            speedFactor=6;
        }
    }
    public void decelerate() {
        float linear=SystemClock.elapsedRealtime()/1000*12.0f;
        speedFactor-=linear/2;
        if (speedFactor<0.5f) {
            speedFactor=0.5f;
        }
    }
    
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        viewFrustum=new GLFrustum(35, (float)width/(float)height, 1.0f, 100.0f);
        projectionViewMatrix.loadMatrix(viewFrustum.getProjectionMatrix());
        transformPipleLine.setMatrixStack(modelViewMatrix, projectionViewMatrix);
        
        screenWidth=width;
        screenHeight=height;
        cameraFrame.moveForward(-1f);
        generateOrtho2DMat();
        initTextures();
    }

    @Override
    public void onDrawFrame(GL10 gl) {
       
        final float totalTime=6;
        final float halfTotalTime=totalTime/2;
        float seconds=SystemClock.elapsedRealtime()/1000*speedFactor;
        float xPos=0;
        
        while (seconds>totalTime) {
            seconds-=totalTime;
        }
        
        if (seconds<halfTotalTime) {
            xPos=seconds-halfTotalTime*0.5f;
        }else {
            xPos=totalTime-seconds-halfTotalTime*0.5f;
        }
        
        modelViewMatrix.pushMatrix();
            Matrix44f cameraMatrix = cameraFrame.getCameraMatrix(false);
            modelViewMatrix.multMatrix(cameraMatrix);
            
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
            shaderManager.useStockShader(ShaderType.TEXTURE_MODULATE, transformPipleLine.getModelViewProjectionMatrix().getArray(),vWhite,0);
            floorBatch.draw();
            drawWorld(xPos,0.0f);
        modelViewMatrix.popMatrix();
        
        GLES20.glReadPixels(0, 0, screenWidth, screenHeight, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, pixelData);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0+getBlueTarget0());
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES11Ext.GL_RGB8_OES, screenWidth, screenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, pixelData);
        
        projectionViewMatrix.pushMatrix();
            projectionViewMatrix.loadIdentity();
            projectionViewMatrix.loadMatrix(orthoMatrix);
            modelViewMatrix.pushMatrix();
                modelViewMatrix.loadIdentity();
                GLES20.glDisable(GLES20.GL_DEPTH_TEST);
                setupBlurProg();
                screenQuad.draw();
                GLES20.glEnable(GLES20.GL_DEPTH_TEST);
            modelViewMatrix.popMatrix();
        projectionViewMatrix.popMatrix();
        
        advanceBlurTarget();
    }
   
    @Override
    public void onSurfaceDestroyed() {
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        for(int i=0;i<7;i++){
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0+i);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        }
        GLES20.glDeleteTextures(1, textures, 0);
        GLES20.glDeleteTextures(6, blurTextures, 0);
    }

    private void generateOrtho2DMat() {
        orthoMatrix = new Matrix44f();
        orthoMatrix.loadOrtho(0, screenWidth, 0, screenHeight, -1, 1);
        
        screenQuad = new GLBatch(GLES20.GL_TRIANGLE_STRIP, 4);
        screenQuad.color(0.0f, 1.0f, 0.0f, 1.0f);
        screenQuad.multiTexCoord(0, 0.0f, 0.0f); 
        screenQuad.vertex(0.0f, 0.0f, 0.0f);

        screenQuad.color(0.0f, 1.0f, 0.0f, 1.0f);
        screenQuad.multiTexCoord(0, 1.0f, 0.0f);
        screenQuad.vertex((float)screenWidth, 0.0f, 0.0f);

        screenQuad.color(0.0f, 1.0f, 0.0f, 1.0f);
        screenQuad.multiTexCoord(0, 0.0f, 1.0f);
        screenQuad.vertex(0.0f, (float)screenHeight, 0.0f);

        screenQuad.color(0.0f, 1.0f, 0.0f, 1.0f);
        screenQuad.multiTexCoord(0, 1.0f, 1.0f);
        screenQuad.vertex((float)screenWidth, (float)screenHeight, 0.0f);
        screenQuad.end();
    }

    private void drawWorld(float xPos,float yPot) {
        Matrix44f matrix = modelViewMatrix.getMatrix();
        Vector4f transfromedLight=new Vector4f();
        Math3D.transformVector4(transfromedLight, vLightPos, matrix);
        modelViewMatrix.pushMatrix();
            modelViewMatrix.translate(0, 0.4f, -2.5f);
            modelViewMatrix.translate(xPos, 0, 0);
            modelViewMatrix.rotate(yPot, 0, 1, 0);
            
            shaderManager.useStockShader(
                    ShaderType.POSITION_LIGHT, 
                    modelViewMatrix.getMatrix().getArray(),
                    projectionViewMatrix.getMatrix().getArray(),
                    transfromedLight.toArray(),
                    vGreen
                    );
            torusBatch.draw();
        modelViewMatrix.popMatrix();
    }
    
    private void setupBlurProg() {
        GLES20.glUseProgram(blurShader);
        GLES20.glUniformMatrix4fv(locMvpMatrix, 1, false, transformPipleLine.getModelViewProjectionMatrix().getArray(), 0);
        GLES20.glUniform1i(locTextureUnit0, getBlueTarget0());
        GLES20.glUniform1i(locTextureUnit1, getBlueTarget1());
        GLES20.glUniform1i(locTextureUnit2, getBlueTarget2());
        GLES20.glUniform1i(locTextureUnit3, getBlueTarget3());
        GLES20.glUniform1i(locTextureUnit4, getBlueTarget4());
        GLES20.glUniform1i(locTextureUnit5, getBlueTarget5());
    }
    
    private void initVertices() {
        float alpha = 0.25f;
        floorBatch = new GLBatch(GLES20.GL_TRIANGLE_FAN, 4);
            floorBatch.color(0.0f, 1.0f, 0.0f, alpha);
            floorBatch.multiTexCoord(0, 0.0f, 0.0f);
            floorBatch.normal(0.0f, 1.0f, 0.0f);
            floorBatch.vertex(-20.0f, -0.41f, 20.0f);

            floorBatch.color(0.0f, 1.0f, 0.0f, alpha);
            floorBatch.multiTexCoord(0, 10.0f, 0.0f);
            floorBatch.normal(0.0f, 1.0f, 0.0f);
            floorBatch.vertex(20.0f, -0.41f, 20.0f);

            floorBatch.color(0.0f, 1.0f, 0.0f, alpha);
            floorBatch.multiTexCoord(0, 10.0f, 10.0f);
            floorBatch.normal(0.0f, 1.0f, 0.0f);
            floorBatch.vertex(20.0f, -0.41f, -20.0f);

            floorBatch.color(0.0f, 1.0f, 0.0f, alpha);
            floorBatch.multiTexCoord(0, 0.0f, 10.0f);
            floorBatch.normal(0.0f, 1.0f, 0.0f);
            floorBatch.vertex(-20.0f, -0.41f, -20.0f);
        floorBatch.end();
        
        torusBatch = new GLTriangleBatch();
        if (!torusBatch.loadDataFromSD("torus_04_015_35_35")) {
            torusBatch=GLTools.makeTorus(0.4f, 0.15f, 35, 35);
            torusBatch.cacheDataToSD("torus_04_015_35_35");
        }
    }
    
    private void initTextures() {
        textures = GLTools.genTextures(1);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
        GLTools.loadDrawableToTexture2D(mContext, 
                R.drawable.marble, 
                GLES20.GL_LINEAR_MIPMAP_LINEAR, 
                GLES20.GL_LINEAR, 
                GLES20.GL_REPEAT, 
                true);
        
        blurTextures = GLTools.genTextures(6);
        pixelData = ByteBuffer.allocateDirect(screenWidth*screenHeight*3);
        for(int i=0;i<6;i++){
            GLES20.glActiveTexture(GLES20.GL_TEXTURE1+i);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, blurTextures[i]);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, screenWidth, screenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, pixelData);
        }
    }
    
    private void initPrograms() {
        String vpSrc = GLShaderManager.readFileFromAssets(mContext,"Blur.vp");
        String fpSrc = GLShaderManager.readFileFromAssets(mContext,"Blur.fp");
        blurShader = GLShaderManager.createProgram(vpSrc, fpSrc,
                new int[]{GLShaderManager.GL_ATTRIBUTE_VERTEX,GLShaderManager.GL_ATTRIBUTE_TEXTURE0}, 
                new String[]{"vVertex","texCoord0"}
        );
        locMvpMatrix = GLES20.glGetUniformLocation(blurShader, "mvpMatrix");
        locTextureUnit0 = GLES20.glGetUniformLocation(blurShader, "textureUnit0");
        locTextureUnit1 = GLES20.glGetUniformLocation(blurShader, "textureUnit1");
        locTextureUnit2 = GLES20.glGetUniformLocation(blurShader, "textureUnit2");
        locTextureUnit3 = GLES20.glGetUniformLocation(blurShader, "textureUnit3");
        locTextureUnit4 = GLES20.glGetUniformLocation(blurShader, "textureUnit4");
        locTextureUnit5 = GLES20.glGetUniformLocation(blurShader, "textureUnit5");
    }
    
    private void advanceBlurTarget() {
        curBlurTarget=(curBlurTarget+1)%6;
    }
    
    private int getBlueTarget0() {
        return 1+(curBlurTarget+5)%6;
    }
    
    private int getBlueTarget1() {
        return 1+(curBlurTarget+4)%6;
    }
    
    private int getBlueTarget2() {
        return 1+(curBlurTarget+3)%6;
    }
    
    private int getBlueTarget3() {
        return 1+(curBlurTarget+2)%6;
    }
    
    private int getBlueTarget4() {
        return 1+(curBlurTarget+1)%6;
    }
    
    private int getBlueTarget5() {
        return 1+curBlurTarget%6;
    }
    
    private GLFrame cameraFrame;
    private GLFrustum viewFrustum;
    private GLMatrixStack modelViewMatrix;
    private GLMatrixStack projectionViewMatrix;
    private GLGeometryTransform transformPipleLine;
    
    private int screenWidth,screenHeight;
    private int curBlurTarget;
    private GLBatch floorBatch;
    private GLTriangleBatch torusBatch;
    private int blurShader;
    private int locMvpMatrix;
    private int locTextureUnit0;
    private int locTextureUnit1;
    private int locTextureUnit2;
    private int locTextureUnit3;
    private int locTextureUnit4;
    private int locTextureUnit5;
    private int[] blurTextures;
    private int[] textures;
    private float speedFactor=1.0f;
    private GLShaderManager shaderManager;
    private float[] vWhite=new float[]{1,1,1,1};
    private float[] vGreen=new float[]{0,1,0,1};
    private Vector4f vLightPos=new Vector4f(0,3,0,1);
    private ByteBuffer pixelData;
    private Matrix44f orthoMatrix;
    private GLBatch screenQuad;
}
